﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using RemoteTestRunner.BusinessLogic.Views;
using RemoteTestRunner.Contracts;
using ICommand = System.Windows.Input.ICommand;

namespace RemoteTestRunner.BusinessLogic.ViewModels
{
    public class RunnerViewModel : INotifyPropertyChanged
    {
        #region Private Attributes

        private IApplicationNavigationService _applicationNavigationService;
        private IFileOpenDialogService _fileOpenDialogService;
        private IRtrSettings _rtrSettings;
        private ITestRunnerWpfView _view;

        #endregion

        #region Constructors

        public RunnerViewModel()
        {
            TestClassesAndMethods = new ObservableCollection<TestClass>();
            TestResultHistory = new ObservableCollection<SelectedItem<TestResultSummary>>();

            // Setup commands
            ExitCommand = new RelayCommand(ExitCommandExecute);
            RunCommand = new RelayCommand(RunCommandExecute, RunCommandCanExecute);
            DeleteSelectedHistoryItemsCommand = new RelayCommand(DeleteSelectedHistoryItemsCommandExecute);
            ShowOptionsCommand = new RelayCommand(ShowOptionsCommandExecute);
            ProjectAdminCommand = new RelayCommand(ProjectAdminCommandExecute);
            ShowHelpTopicsCommand = new RelayCommand(ShowHelpTopicsCommandExecute);
            ShowAboutCommand = new RelayCommand(ShowAboutCommandExecute);
            OpenTestResultsCommand = new RelayCommand(OpenTestResultsCommandExecute);
            ViewDataSourceCommand = new RelayCommand(ViewDataSourceCommandExecute);

            TestResult = new TestResult();
            Projects = new ObservableCollection<Project>();

            PropertyChanged += RunnerViewModel_PropertyChanged;

            Status = "Ready";
        }

        #endregion

        #region Properties

        public ObservableCollection<SelectedItem<TestResultSummary>> TestResultHistory { get; set; }

        public IWpfViewBase View
        {
            get { return _view; }
        }

        #endregion

        #region Commands

        #region DeleteSelectedHistoryItemsCommand

        public ICommand DeleteSelectedHistoryItemsCommand { get; set; }

        private void DeleteSelectedHistoryItemsCommandExecute(object parameters)
        {
            var selectedItems = from item in TestResultHistory
                                where item.IsSelected
                                select item.Item;

            IoC.Container.Resolve<IRtrService>().DeleteTestResultHistoryItems(selectedItems.ToList());

            LoadTestRunHistory();
        }

        #endregion

        #region ExitCommand

        public ICommand ExitCommand { get; set; }

        private void ExitCommandExecute(object parameter)
        {
            _view.Close();
        }

        #endregion

        #region RunCommand

        public ICommand RunCommand { get; set; }

        public bool RunCommandCanExecute(object parameter)
        {
            return SelectedTest != null && TestsRunning == false;
        }

        private void RunCommandExecute(object parameter)
        {
            Status = "Running tests...";

            var userState = new List<object>();

            var runCommand = new RunTestsCommand
                (
                SelectedProject,
                SelectedTest,
                _rtrSettings.TestTimeoutSeconds,
                GetSelectedClassesAndMethods(),
                IoC.Container.Resolve<IRtrService>()
                );

            Action runDelegate = runCommand.Execute;

            userState.Add(runCommand);
            userState.Add(runDelegate);

            runDelegate.BeginInvoke(RunTestCompleted, userState);
        }

        /// <summary>
        ///   Completion method for async RunTest.
        /// </summary>
        /// <param name = "ar">The async result.</param>
        private void RunTestCompleted(IAsyncResult ar)
        {
            Status = "Finished";

            var userState = (List<object>)ar.AsyncState;

            var runTestDelegate = userState[1] as Action;

            if (runTestDelegate != null)
            {
                try
                {
                    runTestDelegate.EndInvoke(ar);

                    var command = userState[0] as RunTestsCommand;

                    if (command != null)
                    {
                        var summary = command.Result;

                        if (summary != null)
                        {
                            _view.Dispatcher.Invoke(new Action(() =>
                                                                   {
                                                                       // Save the run that just completed for selecting.
                                                                       var savedId = summary.TestRunId;

                                                                       // re-populate the test runs now that we have a new one.
                                                                       // TODO: come up with a better way to populate test runs.  Maybe use a file watcher (this might not work for the remote version).
                                                                       LoadTestRunHistory();

                                                                       TestResultSummary =
                                                                           TestResultHistory.FirstOrDefault(
                                                                               result =>
                                                                               result.Item.TestRunId == savedId);

                                                                       if (TestResultSummary != null)
                                                                       {
                                                                           Status =
                                                                               string.Format(
                                                                                   "Test run completed for test run '{0}'",
                                                                                   TestResultSummary.Item.TestRunName);
                                                                       }
                                                                       else
                                                                       {
                                                                           Status = "Test results unknown due to unhandled condition.";
                                                                       }
                                                                   }));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Status = string.Format("Test run completed with errors: '{0}'", ex.Message);

                    MessageBox.Show(ex.ToString());
                }
            }
        }

        #endregion

        #region ProjectAdminCommand

        public ICommand ProjectAdminCommand { get; set; }

        private void ProjectAdminCommandExecute(object parameter)
        {
            var projectAdmin = IoC.Container.Resolve<ProjectAdminViewModel>();
            _applicationNavigationService.ShowProjectAdmin(projectAdmin);
            LoadProjects();
        }

        #endregion

        #region ShowOptionsCommand

        public ICommand ShowOptionsCommand { get; set; }

        private void ShowOptionsCommandExecute(object parameter)
        {
            var viewModel = new OptionsViewModel();
            _applicationNavigationService.ShowOptions(viewModel);
            LoadProjects();
        }

        #endregion

        #region OpenResultsFileCommand

        public ICommand OpenTestResultsCommand { get; set; }

        private void OpenTestResultsCommandExecute(object parameter)
        {
            string fileName = _fileOpenDialogService.ShowFileOpenDialog(null, "*.trx", ".trx",
                                                                        "Test Results (.trx)|*.trx");

            if (!string.IsNullOrEmpty(fileName))
            {
                var results = new TestResultSummary
                                  {
                                      Path = fileName
                                  };

                TestResultSummary = new SelectedItem<TestResultSummary>(results);
            }
        }

        #endregion

        #region ShowHelpTopicsCommand

        public ICommand ShowHelpTopicsCommand { get; set; }

        /// <summary>
        /// Displays on-line help.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        private void ShowHelpTopicsCommandExecute(object parameter)
        {
            _applicationNavigationService.DisplayOnlineHelp();
        }

        #endregion

        #region ShowAboutCommand

        public ICommand ShowAboutCommand { get; set; }

        private void ShowAboutCommandExecute(object parameter)
        {
            var viewModel = new AboutViewModel();
            _applicationNavigationService.ShowAbout(viewModel);
        }

        #endregion

        #region ViewDataSourceCommand

        public ICommand ViewDataSourceCommand { get; set; }

        private void ViewDataSourceCommandExecute(object parameter)
        {
            var datasource = parameter as DataSourceInfo;

            if (datasource != null)
            {
                _applicationNavigationService.ViewDataSource(datasource.DataSourceBaseUri);
            }
        }

        #endregion

        #endregion

        #region Status (INotifyPropertyChanged Property)

        private string _status;

        public string Status
        {
            get { return _status; }
            set
            {
                if (_status != value)
                {
                    _status = value;
                    OnPropertyChanged("Status");
                }
            }
        }

        #endregion

        #region Projects (INotifyPropertyChanged Property)

        private ObservableCollection<Project> _projects;

        public ObservableCollection<Project> Projects
        {
            get { return _projects; }
            set
            {
                if (_projects != value)
                {
                    _projects = value;
                    OnPropertyChanged("Projects");
                }
            }
        }

        #endregion

        #region SelectedProject (INotifyPropertyChanged Property)

        private Project _selectedProject;

        public Project SelectedProject
        {
            get { return _selectedProject; }
            set
            {
                if (_selectedProject != value)
                {
                    _selectedProject = value;
                    OnPropertyChanged("SelectedProject");
                }
            }
        }

        #endregion

        #region SelectedTest (INotifyPropertyChanged Property)

        private Test _selectedTest;

        public Test SelectedTest
        {
            get { return _selectedTest; }
            set
            {
                if (_selectedTest != value)
                {
                    _selectedTest = value;
                    OnPropertyChanged("SelectedTest");
                }
            }
        }

        #endregion

        #region TestClassesAndMethods (INotifyPropertyChanged Property)

        private ObservableCollection<TestClass> _testClassesAndMethods;

        public ObservableCollection<TestClass> TestClassesAndMethods
        {
            get { return _testClassesAndMethods; }
            set
            {
                if (_testClassesAndMethods != value)
                {
                    _testClassesAndMethods = value;
                    OnPropertyChanged("TestClassesAndMethods");
                }
            }
        }

        #endregion

        #region AllClassesAndMethodsSelected (INotifyPropertyChanged Property)

        private bool _allClassesAndMethodsSelected;

        public bool AllClassesAndMethodsSelected
        {
            get { return _allClassesAndMethodsSelected; }
            set
            {
                if (_allClassesAndMethodsSelected != value)
                {
                    _allClassesAndMethodsSelected = value;
                    OnPropertyChanged("AllClassesAndMethodsSelected");
                }
            }
        }

        #endregion

        #region TestResultSummary (INotifyPropertyChanged Property)

        private SelectedItem<TestResultSummary> _testResultSummary;

        public SelectedItem<TestResultSummary> TestResultSummary
        {
            get { return _testResultSummary; }
            set
            {
                if (_testResultSummary != value)
                {
                    _testResultSummary = value;
                    OnPropertyChanged("TestResultSummary");
                }
            }
        }

        #endregion

        #region TestResult (INotifyPropertyChanged Property)

        private TestResult _testResult;

        public TestResult TestResult
        {
            get { return _testResult; }
            set
            {
                if (_testResult != value)
                {
                    _testResult = value;
                    OnPropertyChanged("TestResult");
                }
            }
        }

        #endregion

        #region TestsRunning (INotifyPropertyChanged Property)

        private bool _testsRunning;

        public bool TestsRunning
        {
            get { return _testsRunning; }
            set
            {
                if (_testsRunning != value)
                {
                    _testsRunning = value;
                    OnPropertyChanged("TestsRunning");
                }
            }
        }

        #endregion

        #region ShowDataDrivenOnly (INotifyPropertyChanged Property)

        public bool ShowDataDrivenOnly
        {
            get { return _rtrSettings.FilterTests; }
            set
            {
                if (_rtrSettings.FilterTests != value)
                {
                    _rtrSettings.FilterTests = value;
                    OnPropertyChanged("ShowDataDrivenOnly");
                }
            }
        }

        #endregion

        #region DataSources (INotifyPropertyChanged Property)

        private ObservableCollection<DataSourceInfo> _dataSources;

        public ObservableCollection<DataSourceInfo> DataSources
        {
            get { return _dataSources; }
            set
            {
                if (_dataSources != value)
                {
                    _dataSources = value;
                    OnPropertyChanged("DataSources");
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void LoadProjects()
        {
            var projects = new List<Project>();

            try
            {
                projects = IoC.Container.Resolve<IRtrService>().GetProjects();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error reading projects file\r\n" + ex, "Error reading projects file");
            }

            Projects.Clear();
            projects.ForEach(p => Projects.Add(p));
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void Start(
            ITestRunnerWpfView view,
            IApplicationNavigationService applicationNavigationService,
            IFileOpenDialogService fileOpenDialogService,
            IRtrSettings rtrSettings)
        {
            _view = view;
            _applicationNavigationService = applicationNavigationService;
            _fileOpenDialogService = fileOpenDialogService;
            _rtrSettings = rtrSettings;

            _view.Show();
            LoadProjects();
        }

        private void RunnerViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            try
            {
                switch (e.PropertyName)
                {
                    case "SelectedProject":
                        break;

                    case "SelectedTest":
                        LoadTestClassesAndMethods();
                        LoadTestRunHistory();
                        LoadDataSources();
                        break;

                    case "TestResultSummary":
                        DisplayTestResults();
                        break;

                    case "ShowDataDrivenOnly":
                        LoadTestClassesAndMethods();
                        break;

                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        /// <summary>
        ///   Fills the data sources for the data source menu
        /// </summary>
        private void LoadDataSources()
        {
            //DataSources.Clear();
            DataSources =
                new ObservableCollection<DataSourceInfo>(
                    IoC.Container.Resolve<IRtrService>().GetDataSources(SelectedTest.ConfigPath));
        }

        private void DisplayTestResults()
        {
            if (TestResultSummary == null)
            {
                TestResult = new TestResult();
            }
            else
            {
                TestResult = IoC.Container.Resolve<IRtrService>().GetTestResult(TestResultSummary.Item.Path);
                _view.SetDetailedResults(TestResult.MultiAssertReport);
            }
        }

        private void LoadTestClassesAndMethods()
        {
            TestClassesAndMethods.Clear();
            if (SelectedTest == null)
            {
                // do nothing, leave the list empty
            }
            else
            {
                IList<TestClass> testMethods = IoC.Container.Resolve<IRtrService>().GetTestMethods(SelectedTest);

                // Filter out non-data-driven tests if the option is turned on
                if (_rtrSettings.FilterTests)
                {
                    for (var classIndex = testMethods.Count - 1; classIndex >= 0; classIndex--)
                    {
                        for (var methodIndex = testMethods[classIndex].TestMethods.Count - 1;
                             methodIndex >= 0;
                             methodIndex--)
                        {
                            if (!testMethods[classIndex].TestMethods[methodIndex].DataDriven)
                            {
                                testMethods[classIndex].TestMethods.RemoveAt(methodIndex);
                            }
                        }

                        // Remove any class nodes that no longer have any method nodes
                        if (testMethods[classIndex].TestMethods.Count == 0)
                        {
                            testMethods.RemoveAt(classIndex);
                        }
                    }
                }

                TestClassesAndMethods = new ObservableCollection<TestClass>(testMethods);
            }
        }

        /// <summary>
        ///   Fills the previous test runs.
        /// </summary>
        private void LoadTestRunHistory()
        {
            TestResultHistory.Clear();
            if (SelectedTest == null)
            {
                // nothing to do, leave the list empty.
            }
            else
            {
                IList<TestResultSummary> testResults =
                    IoC.Container.Resolve<IRtrService>().GetTestResultHistory(SelectedProject,
                                                                              SelectedTest);

                // Sort the results by start time descending.
                var orderedResults = testResults.OrderBy(result => result.TestStartTime).Reverse();

                foreach (TestResultSummary result in orderedResults)
                {
                    TestResultHistory.Add(new SelectedItem<TestResultSummary>(result));
                }

                if (TestResultHistory.Count > 0)
                {
                    TestResultSummary = TestResultHistory[0];
                }
            }
        }

        /// <summary>
        ///   Gets the checked nodes.
        /// </summary>
        /// <returns>An IList of all the checked nodes.</returns>
        private List<string> GetSelectedClassesAndMethods()
        {
            var returnList = new List<string>();
            if (AllClassesAndMethodsSelected)
            {
                // root is checked.  Return an empty list so all tests will run.
            }
            else
            {
                foreach (TestClass testClass in TestClassesAndMethods)
                {
                    if (testClass.IsSelected)
                    {
                        // Add whole class
                        returnList.Add(testClass.Name);
                    }
                    else
                    {
                        foreach (TestMethod testMethod in testClass.TestMethods)
                        {
                            if (testMethod.IsSelected)
                            {
                                returnList.Add(testClass.Name + "." + testMethod.Name);
                            }
                        }
                    }
                }
            }

            return returnList;
        }
    }
}